/**
 * 
 * Copyright (C) 2011 Cody Stoutenburg . All rights reserved.
 *
 *       This program is free software; you can redistribute it and/or
 *       modify it under the terms of the GNU Lesser General Public License
 *       as published by the Free Software Foundation; either version 2.1
 *       of the License, or (at your option) any later version.
 *
 *       This program is distributed in the hope that it will be useful,
 *       but WITHOUT ANY WARRANTY; without even the implied warranty of
 *       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *       GNU Lesser General Public License for more details.
 *
 *       You should have received a copy of the GNU Lesser General Public License
 *       along with this program; if not, write to the Free Software
 *       Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 * 
 */
package ca.usherbrooke.goal;

import java.util.ArrayList;
import java.util.List;

import ca.usherbrooke.model.world.IWorldModel;
import ca.usherbrooke.tools.GUID;

/**
 * @author Cody Stoutenburg
 * 
 */
public class BasicGoal extends GUID {
	private static final long serialVersionUID = 1L;

	public static Long INFINITY_TIME = -1L;

	private final Long entity;

	private Long currentTime;
	private final Long duration;

	private Boolean isFail;
	private Boolean isSolve;

	private final Float scoreTotal;
	private final Float scoreFail;

	private final ArrayList<BasicGoal> subGoals;
	private ArrayList<Float> values;

	// value calculated by the IGoal that is not transfert
	private final String goalName;
	private final String description;

	private Float currentScore;
	private Float estimatedScore;
	private Float estimatedDistance;

	private transient IGoal goal;

	/**
	 * 
	 * @param args
	 */
	public BasicGoal(Long entityGuid, Float scoreTotal, Float scoreFail,
			Long duration, IGoal goal, String args) {
		this.goal = goal;

		this.entity = entityGuid;
		this.scoreFail = scoreFail;
		this.scoreTotal = scoreTotal;
		this.duration = duration;

		this.goal.initGoal(this, args);
		subGoals = new ArrayList<BasicGoal>();
		this.currentTime = 0L;
		this.isSolve = false;
		this.isFail = false;

		values = this.goal.getValues();
		goalName = this.goal.getName();
		description = this.goal.getDescription();

		currentScore = 0f;
		estimatedScore = 0f;
		estimatedDistance = 0f;
	}

	public void addSubGoal(BasicGoal goal) {
		goal.addSubGoal(goal);
	}

	protected void realAddSubGoal(BasicGoal goal) {
		subGoals.add(goal);
	}

	public BasicGoal getCurrentGoal() {
		// we find the first sub goal that is not solve or fail
		// if their is no sub goal the current goal is us
		BasicGoal goal = this;
		for (BasicGoal current : subGoals) {
			if (!current.isTerminated()) {
				goal = current.getCurrentGoal();
				break;
			}
		}
		return goal;
	}

	public Boolean isTerminated() {
		return this.isFail || this.isSolve;
	}

	public final Boolean isSolve() {
		return this.isSolve;
	}

	public final Boolean isFail() {
		return this.isFail;
	}

	public final Long getDuration() {
		return this.duration;
	}

	public Long getRemainingTime() {
		if (INFINITY_TIME.equals(duration)) {
			return Long.MAX_VALUE;
		}
		return duration - currentTime;
	}

	public Long getEntity() {
		return entity;
	}

	public String getName() {
		return goalName;
	}

	public String getDescription() {
		return description;
	}

	public Float getEstimatedDistance() {
		return estimatedDistance;
	}

	public List<Float> getValues() {
		return values;
	}

	// ///////////////// SCORE FUNCTIONS /////////////////
	public Float getEstimatedScore() {
		return estimatedScore;
	}

	public Float getCurrentScore() {
		return currentScore;
	}

	public Float getScoreTotal() {
		return scoreTotal;
	}

	public Float getScoreFail() {
		return scoreFail;
	}

	// /////////////// LOCAL FUNCTION ONLY ///////////////
	public final void update(IWorldModel world, Long timeInMs) {
		if (goal != null) {
			if (!isTerminated()) {
				currentTime += timeInMs;
				// verify is time is out
				if (getRemainingTime() <= 0) {
					currentTime = duration;
					isFail = true;
				}

				// verify if goal is solve
				if (!isTerminated()) {
					isSolve = goal.verifyGoalSolve(world);
				}
				// verify if goal is fail
				if (!isTerminated()) {
					isFail = goal.verifyGoalFail(world);
				}

				// refresh all temp values
				values = goal.getValues();
				currentScore = goal.getCurrentScore();
				estimatedScore = goal.getEstimatedScore();
				estimatedDistance = goal.calculateEstimatedDistance(world);
			}
		}
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((currentTime == null) ? 0 : currentTime.hashCode());
		result = prime * result
				+ ((duration == null) ? 0 : duration.hashCode());
		result = prime * result + ((entity == null) ? 0 : entity.hashCode());
		result = prime * result
				+ ((scoreFail == null) ? 0 : scoreFail.hashCode());
		result = prime * result
				+ ((scoreTotal == null) ? 0 : scoreTotal.hashCode());
		result = prime * result
				+ ((subGoals == null) ? 0 : subGoals.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		BasicGoal other = (BasicGoal) obj;
		if (currentTime == null) {
			if (other.currentTime != null)
				return false;
		} else if (!currentTime.equals(other.currentTime))
			return false;
		if (duration == null) {
			if (other.duration != null)
				return false;
		} else if (!duration.equals(other.duration))
			return false;
		if (entity == null) {
			if (other.entity != null)
				return false;
		} else if (!entity.equals(other.entity))
			return false;
		if (scoreFail == null) {
			if (other.scoreFail != null)
				return false;
		} else if (!scoreFail.equals(other.scoreFail))
			return false;
		if (scoreTotal == null) {
			if (other.scoreTotal != null)
				return false;
		} else if (!scoreTotal.equals(other.scoreTotal))
			return false;
		if (subGoals == null) {
			if (other.subGoals != null)
				return false;
		} else if (!subGoals.equals(other.subGoals))
			return false;
		return true;
	}

}
